/*
 * Nsiway NS2009 touchscreen controller driver
 *
 * Copyright (C) 2017 Icenowy Zheng <icenowy@aosc.xyz>
 *
 * Some codes are from silead.c, which is
 *   Copyright (C) 2014-2015 Intel Corporation
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 */

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/input.h>
#include <linux/input-polldev.h>
#include <linux/input/touchscreen.h>
#include <linux/i2c.h>

 /* polling interval in ms */
#define POLL_INTERVAL	30

/* this driver uses 12-bit readout
 * 配置input 子系统的参数，由于ns20
 * 09 是12bit 的ADC检测，配置为fff
 * */
#define MAX_12BIT	0xfff

#define NS2009_TS_NAME	"ns2009_ts"

/* ns2009 数据存放的寄存器地址*/
#define NS2009_READ_X_LOW_POWER_12BIT	0xc0
#define NS2009_READ_Y_LOW_POWER_12BIT	0xd0
#define NS2009_READ_Z1_LOW_POWER_12BIT	0xe0
#define NS2009_READ_Z2_LOW_POWER_12BIT	0xf0

#define NS2009_DEF_X_FUZZ	32
#define NS2009_DEF_Y_FUZZ	16

 /*
 * The chip have some error in z1 value when pen is up, so the data read out
 * is sometimes not accurately 0.
 * This value is based on experiements.
 */
#define NS2009_PEN_UP_Z1_ERR	80

 struct ns2009_data {
	struct i2c_client		*client;
	struct input_dev		*input;

 	struct touchscreen_properties	prop;

 	bool				pen_down;
};
/*i2c 设备 ns2009 对象数据结构 */


/*
 * ns2009 数据读取接口
 * cmd: ns2009 内部寄存器地址 val： 返回的数据
 * */
 static int ns2009_ts_read_data(struct ns2009_data *data, u8 cmd, u16 *val)
{
	u8 raw_data[2];
	int error;

 	error = i2c_smbus_read_i2c_block_data(data->client, cmd, 2, raw_data);
	/* 读取两个字节的长度到 raw_data */
	if (error < 0)
		return error;

 	if (unlikely(raw_data[1] & 0xf))
		return -EINVAL;

 	*val = (raw_data[0] << 4) | (raw_data[1] >> 4);
	//ns2009 设备的数据为2字节

 	return 0;
}

/*
 * 通过I2C读取ns2009 设备寄存器内的值，并主动向input 子系统上报具体的键值
 * 具体读取ns设备里何处地址以及数据的含义需要参考数据手册
 * */
 static int ns2009_ts_report(struct ns2009_data *data)
{
	u16 x, y, z1;
	int ret;

 	/*
	 * NS2009 chip supports pressure measurement, but currently it needs
	 * more investigation, so we only use z1 axis to detect pen down
	 * here.
	 */
	ret = ns2009_ts_read_data(data, NS2009_READ_Z1_LOW_POWER_12BIT, &z1);
	if (ret)
		return ret;

	/*大概意思是使用Z轴上的数据检测是否为中断输入 */
 	if (z1 >= NS2009_PEN_UP_Z1_ERR) {
		ret = ns2009_ts_read_data(data, NS2009_READ_X_LOW_POWER_12BIT,
					  &x);
		if (ret)
			return ret;

 		ret = ns2009_ts_read_data(data, NS2009_READ_Y_LOW_POWER_12BIT,
					  &y);
		if (ret)
			return ret;

 		if (!data->pen_down) {
			input_report_key(data->input, BTN_TOUCH, 1);
			/*触摸时先上报按键btn_touch 按下事件*/
			data->pen_down = true;
		}

 		input_report_abs(data->input, ABS_X, x);
		input_report_abs(data->input, ABS_Y, y);
		/*上报完具体触摸的坐标*/
		input_sync(data->input);
	} else if (data->pen_down) {
		input_report_key(data->input, BTN_TOUCH, 0);
		/*触摸完成后上报按键btn_touch 松开的事件 */
		input_sync(data->input);
		data->pen_down = false;
	}
	return 0;
}
/*
 * ns2009 设备 poll 接口的操作方法
 * */
 static void ns2009_ts_poll(struct input_polled_dev *dev)
{
	struct ns2009_data *data = dev->private;
	int ret;

 	ret = ns2009_ts_report(data);
	if (ret)
		dev_err(&dev->input->dev, "Poll touch data failed: %d\n", ret);
}
/*
 * 通过ns_data配置 input 子系统相关参数并设置相关属性
 * */
 static void ns2009_ts_config_input_dev(struct ns2009_data *data)
{
	struct input_dev *input = data->input;

 	input_set_abs_params(input, ABS_X, 0, MAX_12BIT, NS2009_DEF_X_FUZZ, 0);
	input_set_abs_params(input, ABS_Y, 0, MAX_12BIT, NS2009_DEF_Y_FUZZ, 0);
	/*
	 * input_set_abs_params 是 Linux 内核中 Input 子系统中的一个函数，用于设
	 * 置输入设备上的绝对轴（Absolute Axis）参数。绝对轴通常用于表示触摸屏或
	 * 者手柄等输入设备上的绝对位置信息，例如触摸屏的坐标或手柄的摇杆位置等。
	 * 设置输入设备上绝对轴的参数，包括最小值、最大值、模糊度和平坦度等。设置
	 * 这些参数可以帮助系统正确地解释输入设备发送的绝对位置信息，并进行相应的
	 * 处理和响应。使用 input_set_abs_params 函数时，需要确保传入的参数合法性，
	 * 并根据实际硬件设备的情况设置正确的参数值。通常情况下，这些参数值可以通
	 * 过查询硬件设备的规格或者手册来获取.
	 * */
	touchscreen_parse_properties(input, false, &data->prop);
	/*
	 * 主要用于解析设备树中的属性（Properties）并根据属性设置触摸屏设备的相关参数
	 * */

 	input->name = NS2009_TS_NAME;
	input->phys = "input/ts";
	input->id.bustype = BUS_I2C;
	input_set_capability(input, EV_KEY, BTN_TOUCH);
	/* 设置输入设备支持某种事件类型*/
}
/*
 * 申请poll dev 设备并通过ns2009_data 填充对应数据结构，并向input 子系统注册
 * */
 static int ns2009_ts_request_polled_input_dev(struct ns2009_data *data)
{
	struct device *dev = &data->client->dev;
	struct input_polled_dev *polled_dev;
	int error;

 	polled_dev = devm_input_allocate_polled_device(dev);
	if (!polled_dev) {
		dev_err(dev,
			"Failed to allocate polled input device\n");
		return -ENOMEM;
	}
	data->input = polled_dev->input;

 	ns2009_ts_config_input_dev(data);
	polled_dev->private = data;
	polled_dev->poll = ns2009_ts_poll;
	polled_dev->poll_interval = POLL_INTERVAL;

 	error = input_register_polled_device(polled_dev);
	/*
	 * 用于注册轮询模式的输入设备的函数,调用后内核会注册所提供的轮询输入设备，
	 * 并将其添加到输入子系统中。然后，内核会在指定的时间间隔内定期调用轮询函数，
	 * 以获取输入设备的数据。这种方式适用于一些不支持中断的特殊输入设备，
	 * 例如某些嵌入式系统中的简单按键或开关。
	 * */
	if (error) {
		dev_err(dev,
			"Failed to register polled input device: %d\n",error);
		return error;
	}

 	return 0;
}

 static int ns2009_ts_probe(struct i2c_client *client,
			   const struct i2c_device_id *id)
{
	struct ns2009_data *data;
	struct device *dev = &client->dev;
	int error;

 	if (!i2c_check_functionality(client->adapter,
				     I2C_FUNC_I2C |
				     I2C_FUNC_SMBUS_READ_I2C_BLOCK |
				     I2C_FUNC_SMBUS_WRITE_I2C_BLOCK)) {
		dev_err(dev, "I2C functionality check failed\n");
		return -ENXIO;
	}
	/*check i2c 控制器是否支持上述功能配置,由v3s芯片的数据手册决定 */
 	data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
	if (!data)
		return -ENOMEM;

 	i2c_set_clientdata(client, data);
	data->client = client;
	/* 将 ns2009 设备相关的数据结构赋值到 i2c client 结构体 */
 	error = ns2009_ts_request_polled_input_dev(data);
	if (error)
		return error;

 	return 0;
};

 static const struct i2c_device_id ns2009_ts_id[] = {
	{ "ns2009", 0 },
	{ }
};
MODULE_DEVICE_TABLE(i2c, ns2009_ts_id);

 static struct i2c_driver ns2009_ts_driver = {
	.probe = ns2009_ts_probe,
	.id_table = ns2009_ts_id,
	/* platform 驱动模型里面会通过 id_table member 去匹配设备 platform 模型的设备 
	 * 即 设备树信息
	 * */
	.driver = {
		.name = NS2009_TS_NAME,
	},
};
module_i2c_driver(ns2009_ts_driver);
/*i2c 设备驱动注册入口 */
